package cheme.ProcessModel.Interpreter;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Iterator;

import syntaxtree.Node;
import cheme.ProcessModel.ChemePropertyChangeEvent;
import cheme.ProcessModel.ConsoleModel;
import cheme.ProcessModel.IInterpretable;
import cheme.ProcessModel.IModelElement;
import cheme.ProcessModel.IParent;
import cheme.ProcessModel.IPropertyAware;
import cheme.ProcessModel.ProcessModelConstants;
import cheme.ProcessModel.PropertyAwareObject;
import cheme.ProcessModel.Annotations.Annotation;
import cheme.ProcessModel.Annotations.IAnnotation;
import cheme.ProcessModel.Interpreter.IRule.RuleResult;
import cheme.ProcessModel.Interpreter.Parsers.IParser;
import cheme.ProcessModel.Interpreter.Parsers.ParserFactory;
import cheme.ProcessModel.Visitors.RenderTextVisitor;


//import  cheme.Application.*;

/**
 * 
 * So the idea that is shaping up here is that as model elements are added or 
 * removed from the model, the interpreter hooks in to listen for changes to 
 * made them. When a parseable cahnge is detected, the interpreter can launch a
 * object specific parser (gotten from the parser factory) and have it create a
 * updated node for that object, this will replace the previous AST node and 
 * trigger 1 or more visitors to rebuild all or part of the system including 
 * symboltable updates, notifying listeners to smboltable state changes, and 
 * reevaluating the system.
 * 
 * So the first step is hooking up listeners and ensuring that the interpreter 
 * can hook, unhook and be notified properly
 * 
 * Then parsing of notification model elements
 * 
 * then maintaining the AST
 * 
 * then updates of listeners
 * 
 * then eval
 * 
 * Note that this interpreter is partly a stub for V@ functionality. THough the 
 * parse takes place, it is strictly for syntax checking and the result is not processed.
 * There is not intepretation conde either.
 * 
 * @author Jonathan
 *
 */
public class Interpreter extends PropertyAwareObject 
{
	private LookupTable lookupTable = null;
	private  IModelElement elementRemoved = null;
	private HashMap<IModelElement,String> consoleMap = new HashMap<IModelElement,String>();
	IModelElement modelBase = null;
	private ConsoleModel cosoleObject = new ConsoleModel();
	private IRuleCheckVisitor currrentRuleChecker;
	
	public Interpreter(IModelElement modelBase)
	{
		this.modelBase = modelBase;
		
		/*
		 * This guy is allowed to sit in the lookup table so that 'root level' 
		 * annotations (such as failure of degrees of freedom analysis)
		 * can be placed in a 'top-level' location
		 */
		ModelElementData data = new ModelElementData(this.modelBase);
		getLookupTable().put(this.modelBase, data);
		
		/*
		 * The interpreter listens to add/remove and update events from the table.
		 * It forwards to the list of model element updaters 
		 */
		getLookupTable().addPropertyChangeListener(this);
		
//		addUpdater(new IUpdater()
//		{
//			public void propertyChange(PropertyChangeEvent evt)
//			{
//				System.out.println("Omniscient just heard: " + evt.getPropertyName());
//			}
//			
//			@Override
//			public String toString()
//			{
//				return "omniscient cat is omniscient";
//			}
//		});
	}
	
	private LookupTable getLookupTable()
	{
		if(null == lookupTable)
		{
			lookupTable = new LookupTable();
		}
		
		return lookupTable;
	}
	
	public void printLookupTable()
	{
		getLookupTable().print();
	}
	
	public Iterator<ModelElementData> modelDataIterator()
	{
		return getLookupTable().iterator();
	}
	
	boolean initialAddition = false;
	
	ModelElementData getModelData(IModelElement element)
	{
		ModelElementData data = getLookupTable().get(element);
		
		if(null == data)
		{
			initialAddition = true;
			data = new ModelElementData(element);
			getLookupTable().put(element, data);
			initialAddition = false;
		}
			
		return data;
	}
	
	public ModelElementData getModelData(String elementName)
	{
		return getLookupTable().get(elementName);
	}
	
	void removeModelData(IModelElement element)
	{
		if(getLookupTable().contains(element))
		{
			getLookupTable().remove(element);
			elementRemoved = element;
		}
		
		if(element instanceof IParent)
		{
			for(Object child : ((IParent)element).getChildren())
			{
				if(child instanceof IModelElement)
				{
					if(cosoleObject.removeEntry(element))
					{
						// PA commented for non feedback version
						//cosoleObject.displayMessage();
					}
					removeModelData((IModelElement)child);
				}
			}
		}
	}
	
	void removeModelData(String elementName)
	{
		if(getLookupTable().contains(elementName))
		{
			removeModelData(getLookupTable().get(elementName).getModel());
		}
	}
	
	public boolean modelExists(String elementName)
	{
		return getLookupTable().contains(elementName);
	}
	
	private void setModelError(IModelElement element, IAnnotation errorAnnotation)
	{
		getModelData(element).setErrorAnnotation(errorAnnotation);
		
	}
	
	private void resetModelError(IModelElement element)
	{
		getModelData(element).setErrorAnnotation(null); // PA Commented
	}
	
	IModelElement modelElement = null;
	Node parseResult = null;
	boolean interpretationHalted = false;
	
	public boolean getInterpretationHalted()
	{
		return interpretationHalted;
	}

	public void setInterpretationHalted(boolean interpretationFailure)
	{
		this.interpretationHalted = interpretationFailure;
	}

	/**
	 * Gets the model element, the alteration of which has instigated the current
	 * interpretation process.
	 */
	public IModelElement getModelElement()
	{
		return modelElement;
	}

	/**
	 * Sets the model element, the alteration of which has instigated the current
	 * interpretation process.
	 * 
	 * In the case of rule failure, an annotation indicating the failure message is added to the source model.
	 */
	public void setModelElement(IModelElement modelElement)
	{
		this.modelElement = modelElement;
	}
	
	/**
	 * Gets the result (if any exists, may be null) of the current interpretation process.
	 */
	public Node getParseResult()
	{
		return parseResult;
	}

	/**
	 * Sets the result (if any exists, may be null) of the current interpretation process.
	 */
	public void setParseResult(Node parseResult)
	{
		this.parseResult = parseResult;
	}
	
	private void initInterpretation(IModelElement modelElement)
	{
		setModelElement(modelElement);
		setInterpretationHalted(false);
		setParseResult(null);
	}
	
	private void initiateInterpretation()
	{		
//		System.out.println("Parsing (" + getModelElement().getClass().getName() + ") "+ getModelElement().toString());
		
		/*
		 * The interpreter gets the graph object that triggered the event. This object maintains its hierarchical position
		 * in the graph and this can be used verify that it conforms to a set of rules. Currently, the system supports a 
		 * pre-parse rule check and a post parse rule set. Pre-parse is ran against the object graph, with the event sender
		 * object as the entry point. 
		 * 
		 * Below, the post parse check is ran against the ast and object table that results from a parse. 
		 */
		
		/*
		 * Preparse rule checking.
		 */

		processRules(getModelElement(), PreparseRuleCheckerFactory.getInstance().getRuleChecker(getModelElement()));
		
		preProcessRootRules();
		
		/*
		 * Parse.
		 * Generates a 'parse tree' by parsing individual edited elements and updating a correspondin prase node in the interpreter's data table (modelElementToData)
		 */
		
		if(!getInterpretationHalted())
		{
			setParseResult(parse());
		}
		
		/*
		 * AST processing.
		 * Processes the parse table to yield a AST.
		 * 
		 * What do I hope to accomplish here? Building the symbol table for looking up specific variables by reference.
		 * This is the point where variable decs are added to the symbol table,
		 * refrences to them are registered, listeners for changes added, ...
		 */
		
		if(!getInterpretationHalted() && null != getParseResult())
		{
			/*
			 * So, I need some master visitor to coordinate/initiate visitation of the altered model element
			 */
			interpret();
		}
			
		if(!getInterpretationHalted())
		{
			processRules(getModelElement(), PostparseRuleCheckerFactory.getInstance().getRuleChecker(getModelElement()));
		}
		
		postProcessRootRules();
	}
	
	private void preProcessRootRules()
	{
		if(null == getModelElement() || getModelElement().isInitial())
		{
			return;
		}
		
		processRules(modelBase, PreparseRuleCheckerFactory.getInstance().getRuleChecker(modelBase));
	}
	
	/*
	 * Processgloabal rules applied to the model base. For instance if certain graph
	 * theoretic properties are enforced, this is where they would be instigated.
	 */
	private void postProcessRootRules()
	{
		if(null == getModelElement() || getModelElement().isInitial())
		{
			return;
		}
		
		processRules(modelBase, PostparseRuleCheckerFactory.getInstance().getRuleChecker(modelBase));
	}
	
	private void processRules(IModelElement modelElement, IRuleCheckVisitor ruleChecker)
	{
		if(elementRemoved != modelElement)
		{		
			setCurrentRuleChecker(ruleChecker);
			IRule.RuleResult ruleResult = ruleChecker.check(modelElement);
			
			
			cosoleObject.setModelElement(modelElement);
			
			if(null != ruleResult)
			{
				if(ruleResult instanceof IRule.Failure)
				{
					String strmsgCount = updateConsoleMap(ruleResult);
					// PA commented for non feedback version
					//setModelError(ruleResult.getSource(), new Annotation(ruleResult.getSource(), ProcessModelConstants.DESC_ERROR, ruleResult.getMessage(),strmsgCount));
					setInterpretationHalted(true);
					
					
				}
				else if(ruleResult instanceof IRule.Warning)
				{
					String strmsgCount = updateConsoleMap(ruleResult);			
					// PA commented for non feedback version
					//setModelError(ruleResult.getSource(), new Annotation(ruleResult.getSource(), ProcessModelConstants.DESC_WARNING, ruleResult.getMessage(),strmsgCount));
					
					//setInterpretationHalted(true); //warnings should allow further processing
				}
				else
				{
					resetModelError(ruleResult.getSource());
										
					removeEntry(ruleResult.getSource());					
					 				
								
				}
			}
				
		}
		
		cosoleObject.resetConsole();
		// PA commented for non feedback version
		//cosoleObject.displayMessage();
	}
	

	private void setCurrentRuleChecker(IRuleCheckVisitor ruleChecker)
	{
		currrentRuleChecker = ruleChecker;		
	}
	
	private IRuleCheckVisitor getCurrentRuleChecker()
	{		
		return currrentRuleChecker;
	}

	public void removeEntry(IModelElement elementKey)
	{
		IRuleCheckVisitor ruleChecker = getCurrentRuleChecker();
		// Remove the annotations only in post parse rules
		if ( ruleChecker != null && ! (ruleChecker instanceof PreparseRuleChecker)) 
		{
			boolean bEntryremoved = cosoleObject.removeEntry(elementKey);	
			if (bEntryremoved)
			{
				// if removing a entry from the map..set the flag dirty
				cosoleObject.setFlagDirty();
			}
		}
	}
	


	public String updateConsoleMap(RuleResult ruleResult)
	{
		String strmsgCount = " ";
		if (null != ruleResult)
		{
			IModelElement element = ruleResult.getSource();
			String messageVal = ruleResult.getMessage();			
			
			if (cosoleObject.containsElement(element)&& cosoleObject.containsMessage(element, messageVal))
			{
				strmsgCount = cosoleObject.getMessageIndex(element);
			}
			else
			{
				int messageCount = cosoleObject.createMap(element,messageVal);			
				strmsgCount = Integer.toString(messageCount);
				strmsgCount = strmsgCount + " ";
				// if creating a new entry in map..set the flag dirty
				cosoleObject.setFlagDirty();
			}
		}

			return strmsgCount;			
	}

	/**
	 * Renders the instigating model element's to "Chemical" language and runs the parser agaisnt this representation.
	 * 
	 * In the case of parse failure, an annotation indicating the failure message is added to the source model.
	 */
	//private Node parse() // PA Change
	public Node parse()
	{
		Node result = null;
		IParser parser = ParserFactory.getParser(getModelElement());
		
		if(null == parser)
		{
			return result;
		}
			
		String renderedText = ((String)RenderTextVisitor.getInstance().visit(getModelElement())).trim();
					
		if(null == renderedText || renderedText.isEmpty())
		{
			return result;
		}
		
		result = parser.parse(renderedText);
			
		if(null != result)
		{
			resetModelError(getModelElement());
		}
		else
		{
			// PA commented for non feedback version
			/*setModelError(
					getModelElement(), 
					new Annotation(
							getModelElement(), 
							ProcessModelConstants.DESC_ERROR, parser.getMessage()));*/
			setInterpretationHalted(true);
		}
		
		return result;
	}
	
	private void interpret()
	{
		//use getParseResult() to initiate a visitor...
//		ChemicalVisitor visitor = ParseTreeVisitorFactory.getInstance().getParseTreeVisitor(getModelElement(), getSymbolTable());
		
		/*
		 * So in the context of a declaration, this (appears to) succesffully adds a varaible object to the symbol table
		 * for later reference by other declarations and equations.
		 * 
		 * How to deal with changes to identifiers?
		 * How do deal with removals?
		 * 
		 * I know the originating model elements and can correlate that
		 * 
		 * The symbol table is keyed by name, yet the name can change...
		 * what if 
		 */

//		getParseResult().accept(visitor, null);
	}
	
	public void update(IModelElement modelElement)
	{
		interpretTextChange(modelElement);
	}
	
	public void interpretTextChange(IModelElement modelElement)
	{
		if(null == modelElement || !(modelElement instanceof IInterpretable) || modelElement.isInitial())
		{
			return;
		}
		
		initInterpretation(modelElement);
		initiateInterpretation();
	}
	
	/*
	 * TODO: The interpreter needs to attend to the addition and removal of interpreted model elements as well.
	 * Two options: have the interpreter hook higher level model constructs such as tag, tagcomponentlist  and 
	 * equationlist (creating a new level of IInterpretableModelElementParent), or have the IInterpretableModelElement 
	 * level elements emit a special "I've been added/removed" event that the interpreter attends to...
	 */
	public void interpretChildAddition(IModelElement modelElement)
	{
		interpretTextChange(modelElement);
	}
	
	public void interpretChildRemoval(IModelElement modelElement)
	{		
		preProcessRootRules();	
		
		removeModelData(modelElement);
		
		postProcessRootRules();
		
	}
	
	@Override
	public void propertyChange(PropertyChangeEvent evt)
	{
		if(!initialAddition &&
			evt.getPropertyName().equals(IPropertyAware.P_ELEMENT_ADDED) ||
			evt.getPropertyName().equals(IPropertyAware.P_ELEMENT_REMOVED) || 
			evt.getPropertyName().equals(IPropertyAware.P_ELEMENT_NAME_UPDATED))
		{
			updateFor((ChemePropertyChangeEvent)evt);
		}
	}
	
	public void updateFor(ChemePropertyChangeEvent evt)
	{
		/*
		 * TODO: Construct a message to inform updaters of details, P_LABEL, P_QUANT, P_MATERIAL, P_UNITS, P_EQ_TOKEN, ...
		 * Rather than hard code dependence on these guys, I will try to simply propogate the subEvent to
		 * interpreter listeners.
		 */
		firePropertyChange(evt);
	}
	
	public void addUpdater(IUpdater updater)
	{
//		System.out.println("Adding updater: " + updater.toString());
		
		if(!hasUpdater(updater))
		{
			addPropertyChangeListener(updater);
		}
		
//		printUpdaters();
	}
	
	public void removeUpdater(IUpdater updater)
	{
//		System.out.println("Removing updater: " + updater.toString());
		removePropertyChangeListener(updater);
//		printUpdaters();
	}
	
	private boolean hasUpdater(IUpdater updater)
	{
		for(PropertyChangeListener listener : listeners.getPropertyChangeListeners())
		{
			if(listener instanceof IUpdater && (((IUpdater)listener).equals(updater)))
			{
				return true;
			}
		}
		
		return false;
	}
	
	void printUpdaters()
	{
		int listenersCount = listeners.getPropertyChangeListeners().length;
		
		if(0 != listenersCount)
		{
			//System.out.println(getClass().getSimpleName() + " has " + Integer.toString(listenersCount) + " UPDATER-listener(s).");
			
			for(PropertyChangeListener listener : listeners.getPropertyChangeListeners())
			{
				//System.out.println( "\t + \'" + listener.toString() + "\' is listening to " + getClass().getSimpleName() + ".");
			}
		}
	}
}
